#include <stdio.h>
#include <stdlib.h>
#include <uuid/uuid.h>

#define NIT_SHORT_NAMES
#include <nit/macros.h>
#include <nit/palloc.h>
#include <nit/list.h>

#include "sys.h"

#define ID_INIT(...) { __VA_ARGS__ }

#define INB_CELL_INIT(NAME, FLAGS, HERE, PREV, NEXT)		\
	Inb_cell_tagd NAME = {					\
		.des.type = CELL,				\
		.des.flags = FLAGS,				\
		.cell.here = HERE,				\
		.cell.list.list.next = NEXT,			\
		.cell.list.prev = PREV				\
	}


#define INB_REL_INIT(NAME, FLAGS, SUB, ID)	\
	Inb_rel_tagd NAME = {			\
		.des.type = REL,		\
		.des.flags = FLAGS,		\
		.rel.id = ID,			\
		.rel.sub = SUB			\
	}

#define INB_PUR_INIT(NAME, FLAGS, SUB, ID)	\
	Inb_pur_tagd NAME = {			\
		.des.type = PUR,		\
		.des.flags = FLAGS,		\
		.pur.id = ID,			\
		.pur.sub = SUB			\
	}

#define INB_FRM_INIT(NAME, FLAGS, FLDS, ID)	\
	Inb_frm_tagd NAME = {			\
		.des.type = FRM,		\
		.des.flags = FLAGS,		\
		.frm.id = ID,			\
		.frm.flds = FLDS		\
	}

#define INB_FLD_INIT(NAME, FLAGS, FRM, PUR, ID)	\
	Inb_fld_tagd NAME = {			\
		.des.type = FLD,		\
		.des.flags = FLAGS,		\
		.fld.id = ID,			\
		.fld.frm = FRM,			\
		.fld.pur = PUR			\
	}

#define INB_PRD_INIT(NAME, FLAGS, ARGS, OUT, FUNC, ID)		\
	Inb_prd_tagd NAME = {					\
		.des.type = PRD,				\
		.des.flags = FLAGS,				\
		.prd.id = ID,					\
		.prd.args = ARGS,				\
		.prd.out = OUT,					\
		.prd.d.func = FUNC				\
	}

/* IDs in the base system are set up so that they are easy to check in a switch
 * statement by being 0 except in particular indexs dependent upon the type of
 * the value.
 */

enum { ENUM_REL_AND   = 1, ENUM_REL_OR, ENUM_REL_FRST, ENUM_REL_REST };
enum { ENUM_PUR_AXIOM = 1, ENUM_PUR_ANY };
enum { ENUM_FRM_SYM   = 1, ENUM_FRM_CELL, ENUM_FRM_FRM, ENUM_FRM_PUR,
       ENUM_FRM_FLD, ENUM_FRM_ANY };
enum { ENUM_PRD_CAR   = 1, ENUM_PRD_CDR };

#define BASE_ID(INB_TYPE, VALUE)				\
	{ [INB_TYPE] = ENUM_ ## INB_TYPE ## _ ## VALUE }

#define REL_AND_ID   { [3] = 1 }
#define REL_OR_ID    { 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
#define REL_FRST_ID  { 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
#define REL_REST_ID  { 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }

#define PUR_AXIOM_ID { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
#define PUR_ANY_ID   { 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }

#define FRM_SYM_ID   { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
#define FRM_CELL_ID  { [0] = 1 }
#define FRM_FRM_ID   { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
#define FRM_PUR_ID   { 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
#define FRM_FLD_ID   { 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
#define FRM_ANY_ID   { 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }

/* rel */
INB_REL_INIT(inb_rel_and,  INB_NO_FREE, NULL, BASE_ID(REL, AND));
INB_REL_INIT(inb_rel_or,   INB_NO_FREE, NULL, BASE_ID(REL, OR));
INB_REL_INIT(inb_rel_frst, INB_NO_FREE, NULL, BASE_ID(REL, FRST));
INB_REL_INIT(inb_rel_rest, INB_NO_FREE, NULL, BASE_ID(REL, REST));

/* pur */
INB_PUR_INIT(inb_pur_axiom, INB_NO_FREE, NULL, BASE_ID(PUR, AXIOM));
INB_PUR_INIT(inb_pur_any, INB_NO_FREE, NULL, BASE_ID(PUR, ANY));

/* frm */
INB_FRM_INIT(inb_frm_sym,  INB_NO_FREE, NULL, BASE_ID(FRM, SYM));
INB_FRM_INIT(inb_frm_cell, INB_NO_FREE, NULL, BASE_ID(FRM, CELL));
INB_FRM_INIT(inb_frm_frm,  INB_NO_FREE, NULL, BASE_ID(FRM, FRM));
INB_FRM_INIT(inb_frm_pur,  INB_NO_FREE, NULL, BASE_ID(FRM, PUR));
INB_FRM_INIT(inb_frm_fld,  INB_NO_FREE, NULL, BASE_ID(FRM, FLD));
INB_FRM_INIT(inb_frm_any,  INB_NO_FREE, NULL, BASE_ID(FRM, ANY));

/* prd */
extern Inb_cell_tagd car_out_pur_sub;

static void *
prd_car(Inb_cell *args)
{
	return inb_car(inb_car(args));
}

INB_FLD_INIT(car_args_fld, INB_NO_FREE, INB_FRM_CELL, INB_PUR_ANY, { 0x10 });
INB_CELL_INIT(car_args, INB_NO_FREE, &car_args_fld.fld, NULL, NULL);
INB_CELL_INIT(car_out_pur_sub2, INB_NO_FREE, &car_args_fld.fld,
	      &car_out_pur_sub.cell, NULL);
INB_CELL_INIT(car_out_pur_sub, INB_NO_FREE, INB_REL_FRST, NULL,
	      &car_out_pur_sub2.cell);
INB_PUR_INIT(car_out_pur, INB_NO_FREE, &car_out_pur_sub.cell,
	     ID_INIT(99, 222, 73, 101, 103, 174, 64, 43,
		     156, 68, 105, 63, 199, 110, 70, 50));
INB_FLD_INIT(car_out, INB_NO_FREE, INB_FRM_ANY, &car_out_pur.pur, { 0x20 });
INB_PRD_INIT(inb_prd_car, INB_NO_FREE | INB_PRDF,
	     &car_args.cell, &car_out.fld, prd_car, BASE_ID(PRD, CAR));

INB_PRD_INIT(inb_prd_cdr, INB_NO_FREE | INB_PRDF, NULL, NULL, NULL,
	     BASE_ID(PRD, CDR));

static void
id_print_short(uuid_t id)
{
	char str[36];

	uuid_unparse_lower(id, str);
	printf("%.4s", str);
}

static void
print_cell(Inb_cell *dat)
{
	int i = 0;

	printf("(");

	nit_foreach (dat) {
		if (i++ > 0)
			printf(" ");

		if (inb_type(dat) == CELL)
			inb_print(dat->here);
		else {
			printf(". ");
			inb_print(dat);
			break;
		}
	}

	printf(")");
}

static void
print_frm(Inb_frm *dat)
{
	printf("%%");

	switch (dat->id[FRM]) {
	case ENUM_FRM_SYM:
		if (dat == INB_FRM_SYM) {
			printf("sym");
			break;
		}

		goto other;
	case ENUM_FRM_CELL:
		if (dat == INB_FRM_CELL) {
			printf("cell");
			break;
		}

		goto other;
	case ENUM_FRM_FRM:
		if (dat == INB_FRM_FRM) {
			printf("frm");
			break;
		}

		goto other;
	case ENUM_FRM_PUR:
		if (dat == INB_FRM_PUR) {
			printf("pur");
			break;
		}

		goto other;
	case ENUM_FRM_FLD:
		if (dat == INB_FRM_FLD) {
			printf("fld");
			break;
		}
	case ENUM_FRM_ANY:
		if (dat == INB_FRM_ANY) {
			printf("any");
			break;
		}

	default:
	other:
		inb_print(dat->flds);
	}
}

static void
print_pur(Inb_pur *pur)
{
	char str[36];

	printf("!");

	switch (pur->id[PUR]) {
	case ENUM_PUR_AXIOM:
		if (pur == INB_PUR_AXIOM) {
			printf("axiom");
			break;
		}

		goto other;
	case ENUM_PUR_ANY:
		if (pur == INB_PUR_ANY) {
			printf("any");
			break;
		}
	default:
	other:
		if (pur->sub) {
			inb_print(pur->sub);
		} else {
			printf("id:");
			uuid_unparse_lower(pur->id, str);
			printf("%s", str);
		}
	}
}

static void
print_prd(Inb_prd *prd)
{
	printf("<");

	switch (prd->id[PRD]) {
	case ENUM_PRD_CAR:
		if (prd == INB_PRD_CAR) {
			printf("car");
			break;
		}

		goto other;
	case ENUM_PRD_CDR:
		if (prd == INB_PRD_CDR) {
			printf("cdr");
			break;
		}
	default:
	other:
		id_print_short(prd->id);
	}

	printf(" @");
	inb_print(prd->args);
	printf(" =");
	inb_print(prd->out);

	if (!(inb_des(prd)->flags & INB_PRDF)) {
		printf(" ");
		inb_print(prd->d.bod);
	}

	printf(">");
}

static void
print_fld(Inb_fld *fld)
{
	printf("[");
	print_frm(fld->frm);
	printf(" ");
	print_pur(fld->pur);
	printf(" ");
	id_print_short(fld->id);
	printf("]");
}

static void
print_rel(Inb_rel *rel)
{
	printf("~");

	switch (rel->id[REL]) {
	case ENUM_REL_AND:
		if (rel == INB_REL_AND) {
			printf("and");
			return;
		}

		goto other;
	case ENUM_REL_OR:
		if (rel == INB_REL_OR) {
			printf("or");
			return;
		}

		goto other;
	case ENUM_REL_FRST:
		if (rel == INB_REL_FRST) {
			printf("frst");
			return;
		}
		goto other;
	case ENUM_REL_REST:
		if (rel == INB_REL_REST) {
			printf("rest");
			return;
		}
	default:
	other:
		inb_print(rel->sub);
	}
}


void
inb_print(void *dat)
{
	if (!dat) {
		printf("nil");
		return;
	}

	switch (inb_type(dat)) {
	case CELL:
		print_cell(dat);
		return;
	case FRM:
		print_frm(dat);
		return;
	case PUR:
		print_pur(dat);
		return;
	case PRD:
		print_prd(dat);
		return;
	case FLD:
		print_fld(dat);
		return;
	case REL:
		print_rel(dat);
		return;
	case SYM:
		printf("'%s", ((Inb_sym *) dat)->str);
		return;
	}
}
